/**
 * 此脚本适合从MIT课程下载的HTML文件进行字幕转换
 * 提取字幕、生成ASS字幕文件、下载视频并提取音频
 * https://ocw.mit.edu/courses
 */
const fs = require("fs");
const path = require("path");
const config = JSON.parse(fs.readFileSync(path.join(__dirname, "../..", "config.json"), "utf8"));
const { generateASSStyles } = require("./assStyles");

const { exec } = require("child_process");
const { FFMPEG_PATH } = require("./ffmpeg");

// 将主要逻辑封装为可重用的函数
async function convertHtmlToAss(htmlUrl, inputHtmlDir, outputAssPath, textStyles = {}) {
  const htmlDir = path.join(__dirname, "../..", inputHtmlDir);

  // 如果不存在htmlDir目录，则创建
  await ensureHtmlDirExists(htmlDir);

  const targetHtmlFile = "transcript.html";

  // 清理htmlDir目录
  await cleanHtmlDir(htmlDir, targetHtmlFile);

  // 如果配置了inputHtmlUrl,且inputHtmlUrl不为空
  // 则先从inputHtmlUrl对应的网页内容中解析class为transcript-body的dom的innerText
  console.log("  Parsing htmlUrl...");
  console.log("  htmlUrl: ", htmlUrl);
  let mediaDownloadLink = null;
  if (htmlUrl && htmlUrl.trim() !== "") {
    const result = await parseHtmlContent(htmlUrl);
    mediaDownloadLink = result.mediaDownloadLink;
    
    if (mediaDownloadLink) {
      await updateConfigTitle(result.mainTitle, result.secondTitle);
    }

    const transcriptBody = result.transcriptBody;
    if (!transcriptBody || transcriptBody.trim() === "") {
      throw new Error("  Failed to parse transcriptBody from htmlUrl");
    }

    console.log(`  Parsed htmlUrl content: ${transcriptBody.substring(0, 50)}...`);
    fs.writeFileSync(path.join(htmlDir, targetHtmlFile), transcriptBody, "utf8");
  }
  console.log("  Parsing htmlUrl done.");

  // 如果有媒体下载链接，则下载媒体文件
  if (mediaDownloadLink) {
    await downloadMediaFile(mediaDownloadLink);
  }

  // 并将innerText写入htmlFile
  let htmlFile = path.join(htmlDir, targetHtmlFile);
  console.log(`  Writing htmlFile: ${htmlFile}`);
  if (!htmlFile) {
    throw new Error("  No HTML file found in the directory.");
  }

  let htmlContent = fs.readFileSync(htmlFile, "utf8");
  // htmlContent 去掉所有的换行
  htmlContent = htmlContent.replace(/\n/g, " ");

  // 1. 用0:05,0:07,0:09,0:10等作为分隔符，每一个作为一行字幕
  const { matches, lines } = parseTimeCodes(htmlContent);

  if (!matches || !lines) {
    throw new Error("Could not parse time codes from HTML content");
  }

  const newLines = generateSubtitleLines(matches, lines);
  const mergedLines = mergeShortSubtitles(newLines);

  const assContent = generateAssContent(mergedLines, textStyles);

  // 将assContent 写入config.inputDirDat目录的transcript.ass
  console.log("  正在生成字幕文件...");
  fs.writeFileSync(outputAssPath, assContent);
  console.log("  字幕文件生成完毕.");
}

// 确保HTML目录存在
function ensureHtmlDirExists(htmlDir) {
  if (!fs.existsSync(htmlDir)) {
    console.log(`  ${htmlDir} does not exist. Creating...`);
    fs.mkdirSync(htmlDir, { recursive: true });
  }
}

// 清理HTML目录
function cleanHtmlDir(htmlDir, targetHtmlFile) {
  console.log("  Cleaning htmlDir...");
  if (htmlDir && htmlDir !== "") {
    const files = fs.readdirSync(htmlDir);
    files.forEach(file => {
      if (file !== targetHtmlFile) {
        fs.unlinkSync(path.join(htmlDir, file));
      }
    });
  }
}

// 解析HTML内容
async function parseHtmlContent(htmlUrl) {
  try {
    // 使用puppeteer加载页面并提取内容
    const puppeteer = require("puppeteer");
    const browser = await puppeteer.launch({
      headless: "new",
    });
    const page = await browser.newPage();
    await page.goto(htmlUrl, {
      waitUntil: "networkidle0",
      timeout: 60000,
    });

    // 等待transcript-body元素出现，最多等待10秒
    await page.waitForSelector(".transcript-body", { timeout: 30000 });
    await page.waitForSelector(".course-content-parent-title", { timeout: 30000 });

    // 获取页面HTML内容
    await page.content();

    // 提取transcript-body的文本内容
    const transcriptBodyTimes = await page.$eval(".transcript-body", el => {
      return el.textContent;
    });
    const transcriptBodyLines = await page.$eval(".transcript-body", el => el.textContent);
    const transcriptBody = await page.$eval(".transcript-body", el => el.textContent);
    console.log(`- 获取到 ${transcriptBodyTimes.length} 个时间轴条目`);
    console.log(`- 获取到 ${transcriptBodyLines.length} 行字幕内容`);
    // 尝试提取包含data-downloadlink属性的div元素
    let mediaDownloadLink = null;
    const downloadDiv = await page.$("div[data-downloadlink]");
    if (downloadDiv) {
      mediaDownloadLink = await page.evaluate(el => el.getAttribute("data-downloadlink"), downloadDiv);
      console.log(`  Found media download link: ${mediaDownloadLink}`);
    }

    // 当有 mediaDownloadLink 时，提取标题信息
    let mainTitle = "";
    let secondTitle = "";

    if (mediaDownloadLink) {
      try {
        // 获取主标题 (#course-banner下的h1)
        mainTitle = await page.$eval("#course-banner h1", el => el.innerText).catch(() => "");

        // 更精确地获取副标题 (直接从#course-title下的h2元素)
        secondTitle = await page
          .evaluate(() => {
            // 获取父标题（章节标题）
            const parentTitleElement = document.querySelector(".course-content-parent-title");
            return parentTitleElement ? parentTitleElement.innerText : "";
          })
          .catch(error => {
            console.log("  Error extracting second title:", error);
            return "";
          });
      } catch (titleError) {
        console.warn("  Could not extract title elements:", titleError.message);
      }
    }
    
    await browser.close();

    return {
      transcriptBody,
      mediaDownloadLink,
      mainTitle,
      secondTitle
    };
  } catch (error) {
    console.error("  Error fetching or parsing HTML:", error);
    throw error;
  }
}

// 更新配置标题
function updateConfigTitle(mainTitle, secondTitle) {
  // 组合标题
  if (mainTitle || secondTitle) {
    mainTitle = mainTitle.substring(0, 20) + "...";
    secondTitle = secondTitle.substring(0, 20) + "...";
    const combinedTitle = `${mainTitle}\n${secondTitle}`;

    // 更新 config.json 中的 title 属性
    const configPath = path.join(__dirname, "../..", "config.json");
    const configContent = JSON.parse(fs.readFileSync(configPath, "utf8"));
    configContent.title = combinedTitle;
    fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2), "utf8");
    console.log(`  Updated config title to: ${combinedTitle}`);
  }
}

// 解析时间码
function parseTimeCodes(htmlContent) {
  const regex = /\d+:\d{2}/g;
  const matches = htmlContent.match(regex);
  const lines = htmlContent.split(regex);
  if (lines.length > 0 && lines[0] === "") {
    lines.shift();
  }
  return { matches, lines };
}

// 生成字幕行
function generateSubtitleLines(matches, lines) {
  const newLines = [];
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    const startStr = matches[i].padStart(5, "0");
    if (i === lines.length - 1) {
      const startAssTime = `0:${startStr}.00`;
      const endStrMM = matches[i].split(":")[0];
      const endStrSS = matches[i].split(":")[1];
      const endSS = parseInt(endStrSS) + 5;
      const endStr = `${endStrMM.toString().padStart(2, "0")}:${endSS.toString().padStart(2, "0")}`;
      const endAssTime = `0:${endStr}.00`;
      const newLine = `Dialogue: 0,${startAssTime},${endAssTime},Current,,0,0,0,,${line}\n`;
      newLines.push(newLine);
      continue;
    }
    const startAssTime = `0:${startStr}.00`;
    const endAssTime = `0:${matches[i + 1].padStart(5, "0")}.00`;
    const newLine = `Dialogue: 0,${startAssTime},${endAssTime},Current,,0,0,0,,${line}\n`;
    newLines.push(newLine);
  }
  return newLines;
}

// 合并短时间显示的字幕行
function mergeShortSubtitles(newLines) {
  const mergedLines = [];
  let i = 0;

  console.log(`  开始合并字幕行，总共 ${newLines.length} 行`);

  while (i < newLines.length) {
    const currentLine = newLines[i];
    // 解析当前行的时间信息
    const timeRegex = /Dialogue: 0,([^,]+),([^,]+),/;
    const match = currentLine.match(timeRegex);

    if (match) {
      const startTimeStr = match[1];
      const endTimeStr = match[2];
      const startTime = parseAssTime(startTimeStr);
      const endTime = parseAssTime(endTimeStr);
      const duration = endTime - startTime;

      console.log(`  第 ${i} 行: 时间 ${startTimeStr}-${endTimeStr}, 时长: ${duration.toFixed(2)}s`);
      const maxDuration = 10;
      // 如果当前行显示时间少于3秒，尝试与后续行合并
      if (duration < maxDuration && i < newLines.length - 1) {
        let mergedStartTime = startTime;
        let mergedText = currentLine.split(",").pop().trim();
        let mergedEndTimeStr = endTimeStr;
        let mergedEndTime = endTime;
        let j = i + 1;
        let mergedCount = 1;

        console.log(`  第 ${i} 行时长不足，开始合并`);

        // 继续合并直到总时长达到3秒或没有更多行
        while (j < newLines.length && mergedEndTime - mergedStartTime < maxDuration) {
          const nextLine = newLines[j];
          const nextMatch = nextLine.match(timeRegex);

          if (nextMatch) {
            const nextEndTimeStr = nextMatch[2];
            const nextEndTime = parseAssTime(nextEndTimeStr);
            mergedEndTimeStr = nextEndTimeStr;
            mergedEndTime = nextEndTime;
            const nextText = nextLine.split(",").pop().trim();
            mergedText += " " + nextText;
            mergedCount++;
            console.log(`  合并第 ${j} 行: 时间到 ${nextEndTimeStr}, 总时长: ${(mergedEndTime - mergedStartTime).toFixed(2)}s`);
            j++;
          } else {
            break;
          }
        }

        // 创建合并后的行
        const mergedLine = currentLine.replace(/(Dialogue: 0,[^,]+,)[^,]+,(.*)/, `$1${mergedEndTimeStr},$2`).replace(/,([^,]*)$/, "," + mergedText + "\n");

        console.log(`  完成合并: ${mergedCount} 行合并为1行，时长: ${(mergedEndTime - mergedStartTime).toFixed(2)}s`);
        mergedLines.push(mergedLine);
        i = j;
      } else {
        // 正常添加当前行
        console.log(`  第 ${i} 行时长足够或为最后一行，直接添加`);
        mergedLines.push(currentLine);
        i++;
      }
    } else {
      // 如果无法解析时间，直接添加当前行
      console.log(`  第 ${i} 行无法解析时间，直接添加`);
      mergedLines.push(currentLine);
      i++;
    }
  }

  console.log(`合并完成，总共 ${mergedLines.length} 行`);
  return mergedLines;
}

// 生成ASS内容
function generateAssContent(mergedLines, textStyles) {
  return `[Script Info]
Title: Default Aegisub file
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.601
PlayResX: ${config.projectSettings.videoWidth}
PlayResY: ${config.projectSettings.videoHeight}

[V4+ Styles]
${generateASSStyles(textStyles)}
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text

${mergedLines.join("")}
`;
}

// 辅助函数：解析ASS时间格式为秒数
function parseAssTime(timeStr) {
  // 处理格式如 "0:00:05.00" 或 "0:05.00"
  const parts = timeStr.split(":");
  let hours = 0;
  let minutes, seconds, milliseconds;

  try {
    if (parts.length === 3) {
      // 格式为 H:MM:SS.cc
      hours = parseInt(parts[0]) || 0;
      minutes = parseInt(parts[1]) || 0;
      const secondsAndMilliseconds = parts[2].split(".");
      seconds = parseInt(secondsAndMilliseconds[0]) || 0;
      milliseconds = parseInt(secondsAndMilliseconds[1]) || 0;
    } else if (parts.length === 2) {
      // 格式为 M:SS.cc
      hours = 0;
      minutes = parseInt(parts[0]) || 0;
      const secondsAndMilliseconds = parts[1].split(".");
      seconds = parseInt(secondsAndMilliseconds[0]) || 0;
      milliseconds = parseInt(secondsAndMilliseconds[1]) || 0;
    } else {
      // 格式不正确，返回默认值
      return 0;
    }

    // 修正异常的时间值（如4985秒）
    if (seconds >= 60) {
      const additionalMinutes = Math.floor(seconds / 60);
      seconds = seconds % 60;
      minutes += additionalMinutes;
    }

    if (minutes >= 60) {
      const additionalHours = Math.floor(minutes / 60);
      minutes = minutes % 60;
      hours += additionalHours;
    }

    return hours * 3600 + minutes * 60 + seconds + milliseconds / 100;
  } catch (e) {
    console.error("解析时间出错:", timeStr, e);
    return 0;
  }
}

// 下载媒体文件的函数，带重试机制
async function downloadMediaFile(downloadLink) {
  const originalLink = downloadLink;
  downloadLink = downloadLink.replace("https://archive.org/download/", "https://ia601505.us.archive.org/14/items/");
  console.log("  正在下载媒体文件实际位置: " + downloadLink);
  console.log("  原始链接: " + originalLink);

  // 清空config.inputDirMp3根目录的mp4文件，但不清空子文件夹
  const mp3Dir = path.join(__dirname, "../..", config.inputDirMp3);
  if (fs.existsSync(mp3Dir)) {
    const files = fs.readdirSync(mp3Dir);
    files.forEach(function (file) {
      // 只删除根目录下的.mp4文件
      if (path.extname(file).toLowerCase() === ".mp4") {
        const filePath = path.join(mp3Dir, file);
        const stat = fs.statSync(filePath);
        // 确保只删除根目录下的文件，而不是子文件夹中的文件
        if (stat.isFile()) {
          fs.unlinkSync(filePath);
          console.log("  Deleted existing mp4 file: " + file);
        }
      }
    });
  } else {
    // 如果目录不存在，则创建它
    fs.mkdirSync(mp3Dir, { recursive: true });
  }

  console.log("  Downloading media file from: " + downloadLink);

  // 构造输出文件路径
  const fileName = path.basename(downloadLink).split("?")[0]; // 移除查询参数
  const outputPath = path.join(mp3Dir, fileName);
  const psScriptPath = path.join(mp3Dir, fileName + ".ps1");

  // 创建PowerShell脚本文件
  const powershellScript =
    "$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession\n" +
    '$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0"\n' +
    'Invoke-WebRequest -UseBasicParsing -Uri "' +
    downloadLink +
    '" `\n' +
    "-WebSession $session `\n" +
    "-Headers @{\n" +
    '"authority"="ia601505.us.archive.org"\n' +
    '  "method"="GET"\n' +
    '  "path"="/14/items/MITRES.15-003S16/MITRES_15_003S16_1-2-1_360p.mp4"\n' +
    '  "scheme"="https"\n' +
    '  "accept"="*/*"\n' +
    '  "accept-encoding"="identity;q=1, *;q=0"\n' +
    '  "accept-language"="en-GB,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,en-US;q=0.6"\n' +
    '  "priority"="i"\n' +
    '  "referer"="' +
    downloadLink +
    '"\n' +
    '  "sec-ch-ua"=\'"Not;A=Brand";v="99", "Microsoft Edge";v="139", "Chromium";v="139"\'\n' +
    '  "sec-ch-ua-mobile"="?0"\n' +
    '  "sec-ch-ua-platform"=\'"Windows"\'\n' +
    '  "sec-fetch-dest"="video"\n' +
    '  "sec-fetch-mode"="no-cors"\n' +
    '  "sec-fetch-site"="same-origin"\n' +
    '} -OutFile "' +
    outputPath +
    '"';

  // 写入PowerShell脚本文件
  fs.writeFileSync(psScriptPath, powershellScript);
  console.log("  PowerShell脚本已创建: " + psScriptPath);

  // 首先尝试使用系统PowerShell命令下载（更可靠）
  try {
    console.log("  尝试使用PowerShell下载...");

    // 执行PowerShell脚本
    await new Promise(function (resolve, reject) {
      const command = 'powershell -ExecutionPolicy Bypass -File "' + psScriptPath + '"';
      console.log("  执行命令: " + command);

      exec(command, { maxBuffer: 1024 * 1024 * 100, cwd: mp3Dir }, function (error, stdout, stderr) {
        // 删除临时脚本文件
        if (fs.existsSync(psScriptPath)) {
          fs.unlinkSync(psScriptPath);
        }

        if (error) {
          console.error("PowerShell下载失败: " + error.message);
          console.error("  Error code: " + error.code);
          console.error("  stderr: " + stderr);
          console.error("  stdout: " + stdout);
          reject(error);
        } else {
          console.log("  PowerShell下载成功");
          console.log("  stdout: " + stdout);
          resolve(true);
        }
      });
    });

    // 下载完成后，提取音频文件
    await extractAudioFromVideo(outputPath, mp3Dir);

    return true;
  } catch (psError) {
    // 删除临时脚本文件
    if (fs.existsSync(psScriptPath)) {
      fs.unlinkSync(psScriptPath);
    }
    console.error("  PowerShell下载失败:", psError.message);
    process.exit(1);
  }
}

// 从视频文件中提取音频的函数
async function extractAudioFromVideo(videoPath, outputDir) {
  return new Promise((resolve, reject) => {
    // 清理同目录下的mp3文件，但不清理子文件夹
    if (fs.existsSync(outputDir)) {
      const files = fs.readdirSync(outputDir);
      files.forEach(function (file) {
        // 只删除根目录下的.mp3文件
        if (path.extname(file).toLowerCase() === ".mp3") {
          const filePath = path.join(outputDir, file);
          const stat = fs.statSync(filePath);
          // 确保只删除根目录下的文件，而不是子文件夹中的文件
          if (stat.isFile()) {
            fs.unlinkSync(filePath);
            console.log("  Deleted existing mp3 file: " + file);
          }
        }
      });
    }

    // 获取视频文件名（不含扩展名）
    const videoName = path.basename(videoPath, path.extname(videoPath));
    const audioOutputPath = path.join(outputDir, videoName + ".mp3");

    // 使用FFmpeg提取音频
    const command = `"${FFMPEG_PATH}" -i "${videoPath}" -vn -ar 44100 -ac 2 -ab 192k -f mp3 "${audioOutputPath}"`;
    console.log("  执行音频提取命令: " + command);

    exec(command, { maxBuffer: 1024 * 1024 * 100 }, function (error, stdout, stderr) {
      if (error) {
        console.error("  音频提取失败: " + error.message);
        console.error("  stderr: " + stderr);
        reject(error);
      } else {
        console.log("  音频提取成功: " + audioOutputPath);
        console.log("  stdout: " + stdout);
        resolve();
      }
      fs.unlinkSync(videoPath);
    });
  });
}

// 保持向后兼容的主函数
async function main() {
  try {
    const textStyles = config.projectSettings?.textStyles || {};
    await convertHtmlToAss(config.inputHtmlUrl, config.inputDirHtml, path.join(config.inputDirDat, "transcript.ass"), textStyles);
  } catch (error) {
    console.error("转换过程中出现错误:", error);
    process.exit(1);
  }
}

// 如果直接运行此脚本，则执行main函数
if (require.main === module) {
  main();
}